﻿using System;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.IO;
using System.Web.Mvc;
using System.Web.Routing;

namespace CoolCode.Web.Mvc.UI
{
    /// <summary>
    /// Renders a pager component from an IPagination datasource.
    /// </summary>
    public class Pager : IPageable
    {
        private readonly IPageable _source;
        private readonly HttpRequestBase _request;
        private readonly TextWriter _output;
        private readonly RequestContext _requestContext;
        private readonly RouteValueDictionary _routeValues;

        private string _controllerName;
        private string _actionName;

        private string _css = "pager";
        /*页次：<b>{PageIndex}</b>/<b>{PageCount}</b>页 每页记录：<b>{PageSize}</b>条 记录总数：<b>{TotalRecords}</b>*/
        private string _summaryFormat = "Page <b>{PageIndex}</b>/<b>{PageCount}</b>, Showing {From} - {To} of {TotalRecords} ";
        private string _firstPageText = "<<";
        private string _previousPageText = "<";
        private string _nextPageText = ">";
        private string _lastPageText = ">>";
        private string _pageQueryName = "page";
        private string _position = "left";
        private bool _numericPageVisible = true;
        private bool _summaryInfoVisible = true;
        private bool _inputVisible = true;
        private string _onclick = null;
        private Func<int, string> _urlBuilder;

        /// <summary>
        /// Creates a new instance of the Pager class.
        /// </summary>
        /// <param name="source">The datasource</param>
        /// <param name="context">view context</param>
        public Pager(IPageable source, HttpContextBase httpContext, RequestContext requestContext, RouteValueDictionary routeValues)
        {
            _source = source;
            _request = httpContext.Request;
            _output = httpContext.Response.Output;
            _requestContext = requestContext;
            _routeValues = routeValues;
            _controllerName = (string)_routeValues["controller"];
            _actionName = (string)_routeValues["action"];

            _urlBuilder = CreateDefaultUrl;
            PageIndex = source.PageIndex;
            PageSize = source.PageSize;
            TotalRecords = source.TotalRecords;
        }

        /// <summary>
        /// Specifies the query string parameter to use when generating pager links. The default is 'page'
        /// </summary>
        public Pager QueryParam(string queryStringParam)
        {
            _pageQueryName = queryStringParam;
            return this;
        }

        /// <summary>
        /// Specifies the format to use when rendering a pagination containing multiple pages. 
        /// The default is 'Page {PageIndex}/{PageCount}, Showing {From} - {To} of {TotalRecords}'  
        /// </summary>
        public Pager SummaryFormat(string format)
        {
            _summaryFormat = format;
            return this;
        }

        public Pager SummaryFormat(Func<string> format)
        {
            _summaryFormat = format();
            return this;
        }

        /// <summary>
        /// Text for the 'first' link.
        /// </summary>
        public Pager First(string first)
        {
            _firstPageText = first;
            return this;
        }

        /// <summary>
        /// Text for the 'prev' link
        /// </summary>
        public Pager Previous(string previous)
        {
            _previousPageText = previous;
            return this;
        }

        /// <summary>
        /// Text for the 'next' link
        /// </summary>
        public Pager Next(string next)
        {
            _nextPageText = next;
            return this;
        }

        /// <summary>
        /// Text for the 'last' link
        /// </summary>
        public Pager Last(string last)
        {
            _lastPageText = last;
            return this;
        }

        /// <summary>
        /// Uses a lambda expression to generate the URL for the page links.
        /// </summary>
        /// <param name="urlBuilder">Lambda expression for generating the URL used in the page links</param>
        public Pager Link(Func<int, string> urlBuilder)
        {
            _urlBuilder = urlBuilder;
            return this;
        }

        public Pager Align(string position)
        {
            _position = position;
            return this;
        }

        public Pager Css(string css)
        {
            _css = css;
            return this;
        }

        public Pager NumericPageVisible(bool visible)
        {
            _numericPageVisible = visible;
            return this;
        }

        public Pager SummaryInfoVisible(bool visible)
        {
            _summaryInfoVisible = visible;
            return this;
        }

        public Pager InputVisible(bool visible)
        {
            _inputVisible = visible;
            return this;
        }

        public Pager Url(Func<int, string> builder)
        {
            _urlBuilder = builder;
            return this;
        }

        public Pager OnClick(string script)
        {
            _onclick = script;
            return this;
        }

        public override string ToString()
        {
            Render(_output);
            return null;
        }

        #region Render

        public void Render(TextWriter output)
        {
            output.Write("<div class=\"{0}\" align=\"{1}\">", _css, _position);
            if (this.TotalRecords > 0)
            {
                output.Write("<div class=\"pGroup\">");
                renderPageLink(output, 0, _firstPageText, !HasPreviousPage, "pFirst pButton");
                renderPageLink(output, PageIndex - 1, _previousPageText, !HasPreviousPage, "pPrev pButton");
                output.Write("</div>");

                renderSeparator(output);
                output.Write("<div class=\"pGroup\">");
                output.Write("<span class=\"pcontrol\">");

                //render numeric page
                if (_numericPageVisible)
                {
                    renderNumericPages(output);
                }

                output.Write("</span>");
                output.Write("</div>");
                renderSeparator(output);

                output.Write("<div class=\"pGroup\">");
                renderPageLink(output, PageIndex + 1, _nextPageText, !HasNextPage, "pNext pButton");
                renderPageLink(output, PageCount - 1, _lastPageText, !HasNextPage, "pLast pButton");
                output.Write("</div>");

                if (_inputVisible)
                {
                    //TODO:
                    // renderInput(output);
                }
            }

            if (_summaryInfoVisible)
            {
                renderSeparator(output);
                output.Write("<div class=\"pGroup\">");
                renderSummaryInfo(output);
                output.Write("</div>");
            }
            output.Write("</div>");
        }

        private void renderSeparator(TextWriter output)
        {
            output.Write("<div class=\"btnseparator\"/>");
        }

        private void renderSummaryInfo(TextWriter output)
        {
            string summaryInfo = _summaryFormat
                .Replace("{PageIndex}", (this.PageIndex + 1).ToString())
                .Replace("{PageCount}", this.PageCount.ToString())
                .Replace("{PageSize}", this.PageSize.ToString())
                .Replace("{TotalRecords}", this.TotalRecords.ToString())
                .Replace("{From}", (this.PageSize * this.PageIndex + 1).ToString())
                .Replace("{To}", ((this.PageIndex == this.PageCount - 1) ? this.TotalRecords : (this.PageSize * (this.PageIndex + 1))).ToString());
            string summaryHtml = string.Format("<div class=\"pGroup\"><span class=\"pPageStat\">{0}</span></div>", summaryInfo);
            output.Write(summaryHtml);
        }

        private void renderPageLink(TextWriter output, int pageIndex, string displayText, bool disabled, string css)
        {
            if (disabled)
                css = css + " disabled";
            string content = string.Format("<div class=\"{0}\" title=\"{1}\"></div>", css, displayText);
            renderPageLink(output, pageIndex, content, disabled);
            //if (!disabled)
            //{
            //    if (string.IsNullOrEmpty(_onclick))
            //    {
            //        output.Write("<a href=\"{0}\">{1}</a>", _urlBuilder(pageIndex), displayText);
            //    }
            //    else
            //    {
            //        output.Write("<a href=\"{0}\" onclick=\"{2}return false;\">{1}</a>", _urlBuilder(pageIndex), displayText, _onclick);
            //    }
            //}
            //else
            //{
            //    output.Write("<span class=\"disabled\">{0}</span>", displayText);
            //}
        }

        private void renderPageLink(TextWriter output, int pageIndex, string displayText, bool disabled)
        {
            if (!disabled)
            {
                if (string.IsNullOrEmpty(_onclick))
                {
                    output.Write("<a href=\"{0}\">{1}</a>", _urlBuilder(pageIndex), displayText);
                }
                else
                {
                    output.Write("<a href=\"{0}\" onclick=\"{2}return false;\">{1}</a>", _urlBuilder(pageIndex), displayText, _onclick);
                }
            }
            else
            {
                output.Write("<span class=\"disabled\">{0}</span>", displayText);
            }
        }

        private void renderPageLink(TextWriter output, int pageIndex, bool isCurrentPage)
        {
            string displayText = (pageIndex + 1).ToString();
            //displayText = string.Format("<div class=\"{0}\">{1}</div>", "pButton pNumber", displayText);
            if (!isCurrentPage)
            {
                displayText = string.Format("<div class=\"{0}\"><span>{1}</span></div>", "pButton pNumber", displayText);
                renderPageLink(output, pageIndex, displayText, false);
            }
            else
            {
                //displayText = string.Format("<div class=\"{0}\">{1}</div>", "pButton", displayText);
                output.Write("<div class=\"pButton current\">{0}</div>", displayText);
                //output.Write("<span class=\"current\">{0}</span>", displayText);
            }
        }

        private void renderNumericPages(TextWriter output)
        {
            const int MiddlePageIndex = 5;
            const int PageRange = MiddlePageIndex * 2;
            Func<int, int> clipIfMore = value =>
            {
                if (value > this.PageCount - 1)
                {
                    return this.PageCount - 1;
                }
                return value;
            };

            Func<int, int> clipIfLess = value =>
            {
                if (value < 0)
                {
                    return 0;
                }
                return value;
            };

            int maxpage = clipIfMore(this.PageIndex + MiddlePageIndex);
            int minpage = clipIfLess(this.PageIndex - MiddlePageIndex + 1);
            if (maxpage - minpage + 1 < PageRange)
            {
                if (minpage > 0)
                {
                    minpage = clipIfLess(maxpage - PageRange + 1);
                }
                if (maxpage < this.PageCount - 1)
                {
                    maxpage = clipIfMore(minpage + PageRange - 1);
                }
            }

            if (minpage > 0)
            {
                // output.Write("...");
                string displayText = string.Format("<div class=\"{0}\"><span>{1}</span></div>", "pButton", "...");
                renderPageLink(output, -1, displayText, true );
            }
            for (int i = minpage; i <= maxpage; i++)
            {
                bool isCurrentPage = (i == this.PageIndex);
                renderPageLink(output, i, isCurrentPage);
            }
            if (maxpage < this.PageCount - 1)
            {
                // output.Write("...");
                string displayText = string.Format("<div class=\"{0}\"><span>{1}</span></div>", "pButton", "...");
                renderPageLink(output, -1, displayText, true);
            }
        }
        #endregion

        protected virtual string CreateDefaultUrl(int pageIndex)
        {
            var queryString = _request.QueryString;
            var dict = new RouteValueDictionary(_routeValues);
            //int currentPage =0;
            //if (!string.IsNullOrEmpty(queryString[_pageQueryName]))
            //{
            //    //与相应的QueryString绑定 
            //    foreach (string key in queryString.Keys)
            //        if (queryString[key] != null && !string.IsNullOrEmpty(key))
            //            dict[key] = queryString[key];
            //    int.TryParse(queryString[_pageQueryName], out currentPage);
            //}
            //else
            //{
            //    //获取 ～/Page/{page number} 的页号参数
            //    if (dict.ContainsKey(_pageQueryName))
            //        int.TryParse(dict[_pageQueryName].ToString(), out currentPage);
            //}

            //保留查询字符到下一页
            foreach (string key in queryString.Keys)
                dict[key] = queryString[key];

            dict[_pageQueryName] = pageIndex;
            UrlHelper urlHelper = new UrlHelper(_requestContext);
            string url = urlHelper.Action(_actionName, dict);
            return url;
        }


        #region IPageable Members

        public bool HasPreviousPage
        {
            get
            {
                return (PageIndex > 0);
            }
        }

        public bool HasNextPage
        {
            get
            {
                return (PageIndex + 1 < PageCount);
            }
        }

        public int PageCount
        {
            get
            {
                return (int)Math.Ceiling(TotalRecords / (double)PageSize);
            }
        }

        public int PageIndex
        {
            get;
            private set;
        }

        public int PageSize
        {
            get;
            private set;
        }

        public int TotalRecords
        {
            get;
            private set;
        }

        #endregion
    }
}
